/**
 * Copyright (c) 2010 sonntagsfarben.de
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
 
package de.sonntagsfarben.utils
{
	import de.sonntagsfarben.display.ILoader;

	import flash.events.ErrorEvent;
	import flash.events.Event;
	import flash.events.EventDispatcher;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.utils.Dictionary;

	/**
	 * @author Soenke Kluth <sonntagsfarben.de>
	 * @copyright (c) 2009 Soenke Kluth.
	 */
	[Event(name="complete", type="flash.events.Event")]	

	[Event(name="open", type="flash.events.Event")]	

	[Event(name="progress", type="flash.events.ProgressEvent")]	

	[Event(name="ioError", type="flash.events.IOErrorEvent")]	

	public class LoaderQueue extends EventDispatcher
	{
		public static const VERSION : String = "1.0";
		//
		public static const NAME : String = "LoaderQueue";
		public static const MAX_CONNECTIONS : uint = 12;
		//
		private static var _numInstances : uint;
		private static var _loaders : Dictionary = new Dictionary(true);
		//
		private var _queue : Array;
		private var _loading : Boolean;
		private var _maxConnections : uint;
		private var _currentLoaders : Array;
		private var _name : String;
		private var _itemMap : Dictionary;
		//
		public var destroyOnComplete : Boolean = false;

		public function LoaderQueue(name : String = null, maxConnections : uint = LoaderQueue.MAX_CONNECTIONS)
		{
			if(LoaderQueue._loaders[name] != null)
			{
				throw new Error(name + " already existing!");
			}
			_name = name || NAME + (++_numInstances);
			_maxConnections = maxConnections;
			_init();
		}

		
		/****************************************
		 *			PRIVATE / PROTECTED
		 ****************************************/
		private function _init() : void 
		{
			_queue = new Array();
			_currentLoaders = new Array();
			_itemMap = new Dictionary(true);
			LoaderQueue._loaders[_name] = this;
		}

		
		public static function getLoader(name : String) : LoaderQueue
		{
			return LoaderQueue._loaders[name];
		}

		
		public function getItem(url : String) : ILoader
		{
			return _itemMap[url];
		}

		
		private function _addListener(loader : ILoader) : void
		{
			loader.addEventListener(IOErrorEvent.IO_ERROR, onError, false, 10, true);
			loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError, false, 10, true);
			loader.addEventListener(Event.COMPLETE, onLoaded, false, 10, true);
		}

		
		private function _removeListener(loader : ILoader) : void
		{
			loader.removeEventListener(Event.COMPLETE, onLoaded);
			loader.removeEventListener(IOErrorEvent.IO_ERROR, onError);
			loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError);
		}

		
		protected function _loadNext() : void
		{
			var length : uint = _maxConnections - _currentLoaders.length;
			length = length > _queue.length ? _queue.length : length;
			length = length < 0 ? 0 : length;
			var loader : ILoader;
				
			for (var i : int = 0;i < length ;i++) 
			{
				loader = _queue.shift();
				_addListener(loader);
				_currentLoaders.push(loader);
				loader.load();
			}
		}

		
		/****************************************
		 *			EVENT-HANDLER
		 ****************************************/
		protected function onLoaded( event : Event ) : void
		{
			var loader : ILoader = event.target as ILoader;
			_removeListener(loader);
			
			
			_itemMap[loader.url] = loader;
			
			if(_currentLoaders.indexOf(loader) != -1)
			{
				_currentLoaders.splice(_currentLoaders.indexOf(loader), 1);
			}
			
			//dispatchEvent(new ProgressEvent(ProgressEvent.PROGRESS, false, false, _iterator.index, _queue.length));
			if(_currentLoaders.length + _queue.length > 0)
			{
				_loadNext();
			}
			else
			{
				onComplete();
			}
		}

		
		protected function onError( event : ErrorEvent ) : void
		{
			trace(this + " onError " + event.type);
			var loader : ILoader = event.target as ILoader;
			if(_currentLoaders.indexOf(loader) != -1)
			{
				_currentLoaders.splice(_currentLoaders.indexOf(loader), 1);
			}
			_removeListener(loader);
			dispatchEvent(event);
			_loadNext();
		}

		
		protected function onLoadStart() : void
		{
			_loading = true;
			dispatchEvent(new Event(Event.OPEN));
		}

		
		protected function onComplete() : void
		{
			_loading = false;
			dispatchEvent(new Event(Event.COMPLETE));
			if(destroyOnComplete)
			{
				destroy();
			}
		}

		
		/****************************************
		 *			PUBLIC
		 ****************************************/
		public function start() : void
		{
			if(!loading)
			{
				if(_queue.length == 0)
				{
					trace(this + " no items in queue im not loading");
					return;
				}
				
				onLoadStart();
				_loadNext();
			}
		}

		
		public function add( loader : ILoader ) : void
		{
			if ( loader != null )
			{
				_queue.push(loader);
			}
		}

		
		public function removeAll() : void
		{
			stop();
			_queue.length = 0;
			for (var url : String in _itemMap) 
			{
				delete(_itemMap[url]);
			}
		}

		
		public function stop() : void
		{
			if ( loading )
			{
				for each (var loader : ILoader in _currentLoaders) 
				{
					if ( loader != null )
					{
						if ( loader.loading )
							loader.close();
						_removeListener(loader);
						loader.destroy();
					}
				}
			}

			_currentLoaders.length = 0;
		}

		
		public function destroy() : void
		{
			removeAll();
			_currentLoaders = null;
			_queue = null;
			_itemMap = null;
			delete(LoaderQueue._loaders[_name]);
		}

		
		/****************************************
		 *			GETTER-SETTER
		 ****************************************/
		public function get items() : Array
		{
			var array : Array = [];
			
			for (var url : String in _itemMap) 
			{
				array.push(_itemMap[url]);	
			}			
			
			return array; 
		}

		
		public function get loading() : Boolean
		{
			return _loading;
		}

		
		public function get currentIndex() : int
		{
			return 0;
		}

		
		public function get queueSize() : int
		{
			return _queue.length;
		}

		
		public function get name() : String
		{
			return _name;
		}
	}
}